/*
 * Copyright:  2018 smarabbit studio.
 *
 * Licensed under the Confluent Community License; you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 * http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under
 *  the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 *  either express or implied.  See the License for the specific language governing
 *  permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:    2020/8/17 上午9:19
 *
 */
package com.massyframework.beanskin.graphql.fetching;

import java.util.List;
import java.util.Map;

import org.reactivestreams.Publisher;

import com.massyframework.beanskin.graphql.util.GraphQLContextUtils;

import graphql.execution.DataFetcherResult;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStrategyParameters;
import graphql.execution.MergedField;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLType;
import graphql.schema.idl.SchemaDirectiveWiring;
import graphql.schema.idl.SchemaDirectiveWiringEnvironment;
import reactor.core.publisher.Flux;

public class AutoExtendDirective implements SchemaDirectiveWiring {

    private static final String AUTO_EXTEND = "autoExtend";
    private static final String EXTEND_FIELD = "extendField";
    private DefaultMergedFieldFactory factory = new DefaultMergedFieldFactory();

    public AutoExtendDirective(){
    }

    /**
     * This is called when a field is encountered, which gives the schema directive a chance to modify the shape and behaviour
     * of that DSL element
     * <p>
     * The {@link #onArgument(SchemaDirectiveWiringEnvironment)} callbacks will have been
     * invoked for this element beforehand
     *
     * @param environment the wiring element
     * @return a non null element based on the original one
     */
    @Override
    public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> environment) {

        if (environment.getElement().getDirective(AUTO_EXTEND) != null) {
            DataFetcher<?> dataFetcher = environment.getFieldDataFetcher();
            DataFetcher<?> newFetcher = this.createDataFetcher(dataFetcher);

            environment.getCodeRegistry()
                    .dataFetcher(environment.getFieldsContainer(), environment.getFieldDefinition(), newFetcher);
        }

        return environment.getElement();
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
	protected DataFetcher<?> createDataFetcher(DataFetcher<?> original){
        return env -> {
            Object  result = original.get(env);
            if (result != null) {
                Object value = result instanceof DataFetcherResult ?
                        ((DataFetcherResult<?>) result).getData() : result;
                if (value != null) {                	
                    if (value instanceof Publisher) {
                        Flux flux = Flux.firstWithSignal((Publisher)value)
                                .doOnNext( event -> {
                                    String path = env.getExecutionStepInfo().getPath().toString();
                                    GraphQLContextUtils.setAttribute(env.getContext(), path, factory);
                                });

                        return flux;
                    }else {
                        String path = env.getExecutionStepInfo().getPath().toString();
                        GraphQLContextUtils.setAttribute(env.getContext(), path, factory);
                    }
                }
            }

         
            return  result;
        };
    }


    private class DefaultMergedFieldFactory extends AbstactMergedFieldFactory<Object> {

        /**
         * 创建组合字段
         *
         * @param executionContext {@link ExecutionContext}, 执行上下文
         * @param parameters       {@link ExecutionStrategyParameters}, 执行策略参数
         * @param fetchedObject    {@link java.lang.Object}, 对象实例
         * @param objectType       {@link GraphQLObjectType}, GraphQL类型
         * @return {@link MergedField}, 可能返回null.
         */
        @Override
        public MergedField createMargedField(ExecutionContext executionContext,
                                             ExecutionStrategyParameters parameters,
                                             Object fetchedObject,
                                             GraphQLObjectType objectType) {
            MergedField currentField = parameters.getField();
            Map<String, CustomizeField> fieldMap = this.parseExistFields(currentField);
            this.fullExtendField(objectType, fieldMap);

            return this.doCreate(currentField, fieldMap);
        }

        /**
         * 对{@code objectType}的字段进行遍历，向{@code fieldMap}填充标量和枚举字段
         * @param objectType
         * @param fieldMap
         */
        protected void fullExtendField(GraphQLObjectType objectType, Map<String, CustomizeField> fieldMap){
            List<GraphQLFieldDefinition> definitions = objectType.getFieldDefinitions();
            for (GraphQLFieldDefinition definition: definitions){
                String name = definition.getName();
                if (fieldMap.containsKey(name)){
                    continue;
                }

                if (definition.getArguments().size() > 0){
                    //有参数，不能自动扩展
                    continue;
                }

                GraphQLType type = definition.getType();
                if (type instanceof GraphQLNonNull){
                    type = ((GraphQLNonNull)type).getWrappedType();
                }

                if (type instanceof GraphQLScalarType){
                    CustomizeField field =
                            this.createCustomizeField(name, (GraphQLScalarType)type);
                    if (field != null) {
                        fieldMap.put(field.getName(), field);
                    }
                    continue;
                }

                if (type instanceof GraphQLEnumType){
                    CustomizeField field =
                            this.createCustomizeField(name, (GraphQLEnumType)type);
                    if (field != null) {
                        fieldMap.put(field.getName(), field);
                    }
                    continue;
                }


                if (definition.getDirective(EXTEND_FIELD) != null) {
                    //对象类型
                    if (type instanceof GraphQLObjectType) {
                        CustomizeField field = this.createCustomizeField(definition.getName(), (GraphQLObjectType)type, this);
                        if (field != null) {
                            fieldMap.put(field.getName(), field);
                        }
                        continue;
                    }

                    //数组类型
                    if (type instanceof GraphQLList){
                        CustomizeField field = this.createCustomizeField(definition.getName(), (GraphQLList)type, this);
                        if (field != null) {
                            fieldMap.put(field.getName(), field);
                        }

                        continue;
                    }
                }
            }
        }
    }

}
